/*
 * Copyright (C) 2010 Moduad Co., Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package yxt.push.org.androidpn.client;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;

import yxt.push.org.jivesoftware.smack.ConnectionConfigurationYxt;
import yxt.push.org.jivesoftware.smack.ConnectionListenerYxt;
import yxt.push.org.jivesoftware.smack.PacketListenerYxt;
import yxt.push.org.jivesoftware.smack.XMPPConnectionYxt;
import yxt.push.org.jivesoftware.smack.XMPPExceptionYxt;
import yxt.push.org.jivesoftware.smack.ConnectionConfigurationYxt.SecurityMode;
import yxt.push.org.jivesoftware.smack.filter.AndFilterYxt;
import yxt.push.org.jivesoftware.smack.filter.PacketFilterYxt;
import yxt.push.org.jivesoftware.smack.filter.PacketIDFilterYxt;
import yxt.push.org.jivesoftware.smack.filter.PacketTypeFilterYxt;
import yxt.push.org.jivesoftware.smack.packet.IQYxt;
import yxt.push.org.jivesoftware.smack.packet.PacketYxt;
import yxt.push.org.jivesoftware.smack.packet.RegistrationYxt;
import yxt.push.org.jivesoftware.smack.provider.ProviderManagerYxt;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Handler;
import android.util.Log;

/**
 * This class is to manage the XMPP connection between client and server.
 * 
 * @author Sehwan Noh (devnoh@gmail.com)
 */
public class XmppManager
{

	private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class);

	private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";

	private Context context;

	private NotificationService.TaskSubmitter taskSubmitter;

	private NotificationService.TaskTracker taskTracker;

	private SharedPreferences sharedPrefs;

	private String xmppHost;

	private int xmppPort;

	private XMPPConnectionYxt connection;

	private String username;

	private String password;

	private ConnectionListenerYxt connectionListener;

	private PacketListenerYxt notificationPacketListener;

	private Handler handler;

	private List<Runnable> taskList;

	private boolean running = false;

	private Future<?> futureTask;

	private Thread reconnection;

	public XmppManager(NotificationService notificationService)
	{
		context = notificationService;
		// 获取Task提交管理器，这里用于维护并行任务
		taskSubmitter = notificationService.getTaskSubmitter();

		// Task的计数器
		taskTracker = notificationService.getTaskTracker();

		// 下面都是获取配置信息
		sharedPrefs = notificationService.getSharedPreferences();
		xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
		xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);
		username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
		password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");
		Log.e("", sharedPrefs.getString(Constants.EMULATOR_DEVICE_ID, ""));

		/*
		 * 设置xmpp链接状态的监听器，查看代码发现Xmpp链接状态有5种 1 connectionClosed 2
		 * connectionClosedOnError 3 reconnectingIn 4 reconnectionFailed 5
		 * reconnectionSuccessful
		 */
		connectionListener = new PersistentConnectionListener(this);

		/*
		 * 服务器推送监听器 服务器如果有消息推送，NotificationPacketListener会 自己解析好，并通过XmppManager发送广播
		 */
		notificationPacketListener = new NotificationPacketListener(this);
		// 当xmpp因异常重新连接服务器时，这期间发生异常的话，会在这个handler中处理
		handler = new Handler();

		// 任务队列
		taskList = new ArrayList<Runnable>();

		/*
		 * 当xmppManager因异常与服务器断开链接时 ReconnectionThread会在一定的时间内尝试重新连接
		 * 也就是说，当PersistentConnectionListener监听器监听到异常断开连接
		 * 会调用ReconnectionThread中重新连接的方法以进行连接尝试
		 */
		reconnection = new ReconnectionThread(this);
		// reconnection.run();
	}

	public Context getContext()
	{
		return context;
	}

	// 开始与服务器进行xmpp长链接
	public void connect()
	{
		Log.d(LOGTAG, "connect()...");
		submitLoginTask();
	}

	public void disconnect()
	{
		Log.d(LOGTAG, "disconnect()...");
		terminatePersistentConnection();
	}

	public void terminatePersistentConnection()
	{
		Log.d(LOGTAG, "terminatePersistentConnection()...");
		Runnable runnable = new Runnable()
		{

			final XmppManager xmppManager = XmppManager.this;

			public void run()
			{
				if (xmppManager.isConnected())
				{
					Log.d(LOGTAG, "terminatePersistentConnection()... run()");
					xmppManager.getConnection().removePacketListener(xmppManager.getNotificationPacketListener());
					xmppManager.getConnection().disconnect();
				}
				xmppManager.runTask();
			}

		};
		addTask(runnable);
	}

	public XMPPConnectionYxt getConnection()
	{
		return connection;
	}

	public void setConnection(XMPPConnectionYxt connection)
	{
		this.connection = connection;
	}

	public String getUsername()
	{
		return username;
	}

	public void setUsername(String username)
	{
		this.username = username;
	}

	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}

	public ConnectionListenerYxt getConnectionListener()
	{
		return connectionListener;
	}

	public PacketListenerYxt getNotificationPacketListener()
	{
		return notificationPacketListener;
	}

	public void startReconnectionThread()
	{
		synchronized (reconnection)
		{
			if (!reconnection.isAlive())
			{
				reconnection.setName("Xmpp Reconnection Thread");
				reconnection.start();
			}
		}
	}

	public Handler getHandler()
	{
		return handler;
	}

	public void reregisterAccount()
	{
		removeAccount();
		submitLoginTask();
		runTask();
	}

	public List<Runnable> getTaskList()
	{
		return taskList;
	}

	public Future<?> getFutureTask()
	{
		return futureTask;
	}

	public void runTask()
	{
		Log.d(LOGTAG, "runTask()...");
		synchronized (taskList)
		{
			running = false;
			futureTask = null;
			if (!taskList.isEmpty())
			{
				Runnable runnable = (Runnable) taskList.get(0);
				taskList.remove(0);
				running = true;
				futureTask = taskSubmitter.submit(runnable);
				if (futureTask == null)
				{
					taskTracker.decrease();
				}
			}
		}
		taskTracker.decrease();
		Log.d(LOGTAG, "runTask()...done");
	}

	private String newRandomUUID()
	{
		// String uuidRaw = UUID.randomUUID().toString();
		// return uuidRaw.replaceAll("-", "");
		// edit by LM 2012-12-05 uuid使用手机的ID
		String uuid = sharedPrefs.getString(Constants.DEVICE_ID, "");
		return uuid.replaceAll("-", "");

	}

	private boolean isConnected()
	{
		boolean relust = false;
		relust = (connection != null && connection.isConnected());
		Log.d(LOGTAG, "connect---------" + relust);
		return relust;
	}

	private boolean isAuthenticated()
	{
		boolean relust = false;
		relust = (connection != null && connection.isConnected() && connection.isAuthenticated());
		Log.d(LOGTAG, "authentcate----------" + relust);
		return relust;
	}

	private boolean isRegistered()
	{
		boolean relust = false;
		relust = sharedPrefs.contains(Constants.XMPP_USERNAME) && sharedPrefs.contains(Constants.XMPP_PASSWORD);
		Log.d(LOGTAG, "register------------" + relust);
		return relust;
	}

	private void submitConnectTask()
	{
		Log.d(LOGTAG, "submitConnectTask()...");
		addTask(new ConnectTask());
	}

	private void submitRegisterTask()
	{
		Log.d(LOGTAG, "submitRegisterTask()...");
		submitConnectTask();
		addTask(new RegisterTask());
	}

	private void submitLoginTask()
	{
		Log.d(LOGTAG, "submitLoginTask()...");
		submitRegisterTask();
		addTask(new LoginTask());
	}

	private void addTask(Runnable runnable)
	{
		Log.d(LOGTAG, "addTask(runnable)...");
		taskTracker.increase();
		synchronized (taskList)
		{
			if (taskList.isEmpty() && !running)
			{
				running = true;
				futureTask = taskSubmitter.submit(runnable);
				if (futureTask == null)
				{
					taskTracker.decrease();
				}
			}
			else
			{
				// runTask();

				taskList.add(runnable);
			}
		}
		Log.d(LOGTAG, "addTask(runnable)... done");
	}

	private void removeAccount()
	{
		Editor editor = sharedPrefs.edit();
		editor.remove(Constants.XMPP_USERNAME);
		editor.remove(Constants.XMPP_PASSWORD);
		editor.commit();
	}

	/**
	 * A runnable task to connect the server.
	 */
	private class ConnectTask implements Runnable
	{

		final XmppManager xmppManager;

		private ConnectTask()
		{
			this.xmppManager = XmppManager.this;
		}

		public void run()
		{
			Log.i(LOGTAG, "ConnectTask.run()...");

			if (!xmppManager.isConnected())
			{
				// Create the configuration for this new connection
				ConnectionConfigurationYxt connConfig = new ConnectionConfigurationYxt(xmppHost, xmppPort);
				connConfig.setSecurityMode(SecurityMode.disabled);
				// connConfig.setSecurityMode(SecurityMode.required);
				connConfig.setSASLAuthenticationEnabled(false);
				connConfig.setCompressionEnabled(false);

				XMPPConnectionYxt connection = new XMPPConnectionYxt(connConfig);
				xmppManager.setConnection(connection);

				try
				{
					// Connect to the server
					connection.connect();
					Log.i(LOGTAG, "XMPP connected successfully");

					// packet provider
					ProviderManagerYxt.getInstance().addIQProvider("notification", "androidpn:iq:notification",
							new NotificationIQProvider());

				}
				catch (XMPPExceptionYxt e)
				{
					Log.e(LOGTAG, "XMPP connection failed", e);
				}

				xmppManager.runTask();

			}
			else
			{
				Log.i(LOGTAG, "XMPP connected already");
				xmppManager.runTask();
			}
		}
	}

	/**
	 * A runnable task to register a new user onto the server.
	 */
	private class RegisterTask implements Runnable
	{

		final XmppManager xmppManager;

		private RegisterTask()
		{
			xmppManager = XmppManager.this;
		}

		public void run()
		{
			Log.i(LOGTAG, "RegisterTask.run()...");

			if (!xmppManager.isRegistered())
			{
				final String newUsername = newRandomUUID();
				final String newPassword = newRandomUUID();

				RegistrationYxt registration = new RegistrationYxt();

				PacketFilterYxt packetFilter = new AndFilterYxt(new PacketIDFilterYxt(registration.getPacketID()), new PacketTypeFilterYxt(
						IQYxt.class));

				PacketListenerYxt packetListener = new PacketListenerYxt()
				{

					public void processPacket(PacketYxt packet)
					{
						Log.d("RegisterTask.PacketListener", "processPacket().....");
						Log.d("RegisterTask.PacketListener", "packet=" + packet.toXML());

						if (packet instanceof IQYxt)
						{
							IQYxt response = (IQYxt) packet;
							if (response.getType() == IQYxt.Type.ERROR)
							{
								if (!response.getError().toString().contains("409"))
								{
									Log.e(LOGTAG, "Unknown error while registering XMPP account! " + response.getError().getCondition());
								}
							}
							else if (response.getType() == IQYxt.Type.RESULT)
							{
								xmppManager.setUsername(newUsername);
								xmppManager.setPassword(newPassword);
								Log.d(LOGTAG, "username=" + newUsername);
								Log.d(LOGTAG, "password=" + newPassword);

								Editor editor = sharedPrefs.edit();
								editor.putString(Constants.XMPP_USERNAME, newUsername);
								editor.putString(Constants.XMPP_PASSWORD, newPassword);
								editor.commit();
								Log.i(LOGTAG, "Account registered successfully");
								xmppManager.runTask();
							}
						}
					}
				};

				connection.addPacketListener(packetListener, packetFilter);

				registration.setType(IQYxt.Type.SET);
				// registration.setTo(xmppHost);
				// Map<String, String> attributes = new HashMap<String, String>();
				// attributes.put("username", rUsername);
				// attributes.put("password", rPassword);
				// registration.setAttributes(attributes);
				registration.addAttribute("username", newUsername);
				registration.addAttribute("password", newPassword);
				
				String accountname = sharedPrefs.getString(Constants.ACCOUNTNAME, "");
//				String hospitalcode = sharedPrefs.getString(Constants.HOSPITALCODE, "");
				String pcard = sharedPrefs.getString(Constants.PCARD, "");

				String mobilephone = sharedPrefs.getString(Constants.MOBILEPHONE, "");
//				registration.addAttribute("hospitalcode", hospitalcode);

				registration.addAttribute("accountname", accountname);
				registration.addAttribute("pcard", pcard);
				registration.addAttribute("mobilephone", mobilephone);
				connection.sendPacket(registration);

			}
			else
			{
				Log.i(LOGTAG, "Account registered already");
				xmppManager.runTask();
			}
		}
	}

	/**
	 * A runnable task to log into the server.
	 */
	private class LoginTask implements Runnable
	{

		final XmppManager xmppManager;

		private LoginTask()
		{
			this.xmppManager = XmppManager.this;

		}

		public void run()
		{
			Log.i(LOGTAG, "LoginTask.run()...");

			if (!xmppManager.isAuthenticated())
			{
				Log.d(LOGTAG, "username=" + username);
				Log.d(LOGTAG, "password=" + password);

				try
				{
					xmppManager.getConnection().login(xmppManager.getUsername(), xmppManager.getPassword(), XMPP_RESOURCE_NAME);
					Log.d(LOGTAG, "Loggedn in successfully");

					// connection listener
					if (xmppManager.getConnectionListener() != null)
					{
						xmppManager.getConnection().addConnectionListener(xmppManager.getConnectionListener());
					}

					// packet filter
					PacketFilterYxt packetFilter = new PacketTypeFilterYxt(NotificationIQ.class);
					// packet listener
					PacketListenerYxt packetListener = xmppManager.getNotificationPacketListener();
					connection.addPacketListener(packetListener, packetFilter);

					if (!getConnection().isConnected())
					{
						xmppManager.runTask();// 新增于11-28
					}
					xmppManager.runTask();

				}
				catch (XMPPExceptionYxt e)
				{
					Log.e(LOGTAG, "LoginTask.run()... xmpp error");
					Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage());
					String INVALID_CREDENTIALS_ERROR_CODE = "401";
					String errorMessage = e.getMessage();
					if (errorMessage != null && errorMessage.contains(INVALID_CREDENTIALS_ERROR_CODE))
					{
						xmppManager.reregisterAccount();
						return;
					}
					xmppManager.startReconnectionThread();

				}
				catch (Exception e)
				{
					Log.e(LOGTAG, "LoginTask.run()... other error");
					Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage());
					xmppManager.startReconnectionThread();
				}
				xmppManager.runTask();// 新增于11-28
			}
			else
			{
				Log.i(LOGTAG, "Logged in already");
				xmppManager.runTask();
			}

		}
	}

}
